/*
 * Copyright 2007 Rob Nielsen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.proxymatic.apps.data;

import com.googlecode.proxymatic.apps.data.calculators.BasicFieldNameCalculator;
import com.googlecode.proxymatic.apps.data.handlers.BackingMapAccessMethodHandler;
import com.googlecode.proxymatic.apps.data.handlers.DataAccessMethodHandler;
import com.googlecode.proxymatic.apps.data.handlers.DataStoreMethodHandler;
import com.googlecode.proxymatic.apps.data.handlers.DataToStringMethodHandler;
import com.googlecode.proxymatic.core.AbstractProxyApplication;
import com.googlecode.proxymatic.core.HandlerConfiguration;
import com.googlecode.proxymatic.core.handlers.method.ToStringMethodHandler;
import com.googlecode.proxymatic.core.util.ReflectUtil;
import com.googlecode.proxymatic.core.util.StringUtil;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class ProxyDataFactory extends AbstractProxyApplication implements DataFactory {
    private static final BasicFieldNameCalculator FIELD_NAME_CALCULATOR = new BasicFieldNameCalculator();

    protected void setUpHandlerConfiguration(HandlerConfiguration handlerConfiguration) {
        handlerConfiguration.removeHandlers(ToStringMethodHandler.class);
        handlerConfiguration.addHandler(new DataToStringMethodHandler());
        handlerConfiguration.addHandler(new BackingMapAccessMethodHandler());
        handlerConfiguration.addHandler(new DataAccessMethodHandler());
        handlerConfiguration.addHandler(new DataStoreMethodHandler());
    }

    protected String getProxyType() {
        return "Data";
    }

    public <T> T createData(Class<T> dataType) {
        return createData(dataType, null);
    }

    public <T> T createData(Class<T> dataType, Map backingMap) {
        return createData(dataType, backingMap, FIELD_NAME_CALCULATOR);
    }

    public <T> T createData(Class<T> dataType, Map backingMap, FieldNameCalculator fieldNameCalculator) {
        boolean internalBackingMap = backingMap == null;
        if (internalBackingMap) {
            backingMap = new HashMap();
        }
        validate(dataType, backingMap, fieldNameCalculator, internalBackingMap);
//        return getProxyBuilder().proxy(dataType, backingMap, fieldNameCalculator);
        return dataType.cast(create(proxy().implementingInterface(dataType).withImplementationObjects(backingMap, fieldNameCalculator)));
    }

    private void validate(Class dataType, Map backingMap, FieldNameCalculator fieldNameCalculator, boolean selfBackingMap) {
        Map<String, Class> getMap = new HashMap<String, Class>();
        Map<String, Class> setMap = new HashMap<String, Class>();
        Method[] methods = dataType.getMethods();
        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            String keyName = fieldNameCalculator.getBackingMapKeyName(method);
            if (fieldNameCalculator.isGetter(method)) {
                getMap.put(keyName, method.getReturnType());
            } else if (fieldNameCalculator.isSetter(method)) {
                setMap.put(keyName, method.getParameterTypes()[0]);
            }
        }
        boolean requiresMatchingGettersAndSetters = selfBackingMap && !BackedByMap.class.isAssignableFrom(dataType);
        if (requiresMatchingGettersAndSetters) {
            if (!getMap.keySet().equals(setMap.keySet())) {
                throw new IllegalArgumentException("You must provide a getter and setter for each property if a backing" +
                        " map is not provided and the target interface does not extend " + BackedByMap.class.getName() + ".  Getters: " + StringUtil.sentenceQuote(getMap.keySet(), "'") +
                        ", Setters: " + StringUtil.sentenceQuote(setMap.keySet(), "'"));
            }
        }
        for (String key : getMap.keySet()) {
            Class getClass = getMap.get(key);
            Class setClass = setMap.get(key);
            Object backingObject = backingMap.get(key);
            // FIX ROB Dup with DataAccessMethodHandler
            if (backingObject != null && !ReflectUtil.isInstanceOrBoxed(getClass, backingObject)) {
                throw new IllegalArgumentException("Invalid object in backing map for field '" + key + "'. Expected " + getClass + " but got '" + backingObject + "' (" + backingObject.getClass() + ")");
            }
            if (setClass != null && !getClass.equals(setClass)) {
                throw new IllegalArgumentException("Getter returns " + getClass + " but setter sets " + setClass);
            }
        }
    }
}
